
// ===== 技术指标计算工具函数 =====

/**
 * 计算相对强弱指数(RSI)
 * RSI是衡量价格变动速度和变化的摆动指标，用于判断超买超卖状态
 *
 * @param {Array} klines - K线数据数组，每个元素需包含 {c: close} 属性
 * @param {number} period - 计算周期，默认14
 * @returns {number|null} RSI值(0-100)，数据不足时返回null
 */
function calcRSI(klines, period = 14) {
  // 参数验证：确保输入是有效的数组
  if (!Array.isArray(klines) || klines.length === 0) return null;
  // RSI计算需要至少period+1根K线（因为要计算价格变化）
  if (klines.length < period + 1) return null;

  let gains = 0, losses = 0; // 累计涨幅和跌幅
  const startIdx = klines.length - period; // 计算起始索引

  // 缓存前一个收盘价，避免重复parseFloat调用，提高性能
  let prevClose = parseFloat(klines[startIdx - 1].c);

  // 遍历最近period个K线，累计涨跌幅
  for (let i = startIdx; i < klines.length; i++) {
    const currentClose = parseFloat(klines[i].c);
    const diff = currentClose - prevClose; // 价格变化

    if (diff >= 0) {
      gains += diff; // 累计上涨幅度
    } else {
      losses -= diff; // 累计下跌幅度（取正值）
    }

    prevClose = currentClose; // 更新前一收盘价
  }

  // 计算平均涨幅和平均跌幅
  const avgGain = gains / period;
  const avgLoss = losses / period;

  // 特殊情况：如果没有跌幅，RSI为100
  if (avgLoss === 0) return 100;

  // 计算相对强度和RSI值
  const rs = avgGain / avgLoss; // 相对强度 = 平均涨幅 / 平均跌幅
  return +(100 - (100 / (1 + rs))).toFixed(2); // RSI公式，保留2位小数
}

/**
 * 计算KDJ随机指标
 * KDJ是一个超买超卖指标，由K、D、J三条线组成，用于判断价格走势
 * K线：快速线，反应敏感
 * D线：K线的平滑线，反应相对缓慢
 * J线：方向性指标，反应最敏感
 *
 * @param {Array} klines - K线数据数组，需包含 {h: high, l: low, c: close} 属性
 * @param {number} n - 计算周期，默认9
 * @param {number} kPeriod - K线平滑周期，默认3
 * @param {number} dPeriod - D线平滑周期，默认3
 * @returns {Object} {K, D, J} 指标值，数据不足时返回{K: null, D: null, J: null}
 */
function calcKDJ(klines, n = 9, kPeriod = 3, dPeriod = 3) {
  // 参数验证：需要至少n根K线数据
  if (!Array.isArray(klines) || klines.length < n) return { K: null, D: null, J: null };

  // 初始化K和D的初始值为50（中性值）
  let K = 50, D = 50;
  const startIdx = klines.length - n; // 计算起始位置

  // 预计算常数，提高计算效率
  const kMultiplier = (kPeriod - 1) / kPeriod; // K线历史权重
  const dMultiplier = (dPeriod - 1) / dPeriod; // D线历史权重
  const oneOverK = 1 / kPeriod;                // K线新值权重
  const oneOverD = 1 / dPeriod;                // D线新值权重

  // 逐个计算每个周期的KDJ值
  for (let i = startIdx; i < klines.length; i++) {
    const start = Math.max(0, i - n + 1);
    const periodSlice = klines.slice(start, i + 1); // 获取n个周期的数据

    // 优化：使用单次遍历找最高和最低值，避免多次map操作
    let high = -Infinity, low = Infinity;
    for (const kline of periodSlice) {
      const h = parseFloat(kline.h);
      const l = parseFloat(kline.l);
      if (h > high) high = h;
      if (l < low) low = l;
    }

    const close = parseFloat(klines[i].c);
    // 计算RSV（未成熟随机值）= (当前收盘-最低价) / (最高价-最低价) * 100
    const RSV = high === low ? 0 : ((close - low) / (high - low)) * 100;

    // 优化的平滑计算：使用预计算的常数
    K = RSV * oneOverK + K * kMultiplier;   // K = (RSV + (kPeriod-1) * K_prev) / kPeriod
    D = K * oneOverD + D * dMultiplier;     // D = (K + (dPeriod-1) * D_prev) / dPeriod
  }

  // J线 = 3*K - 2*D，体现方向性和敏感性
  const J = 3 * K - 2 * D;
  return { K: +K.toFixed(2), D: +D.toFixed(2), J: +J.toFixed(2) };
}

/**
 * 计算增强版双KD指标（变速+过滤法）
 * 结合成交量放量、价格位置、EMA趋势进行综合判断
 * 
 * @param {Array} klines - K线数据数组
 * @returns {Object} 包含快KD、慢KD和增强过滤后信号的对象
 */
function calcDualKD(klines) {
  // 快KD (3,1,1)：捕捉瞬时反转
  const fastKD = calcKDJ(klines, 3, 1, 1);
  
  // 慢KD (9,3,3)：判断大方向
  const slowKD = calcKDJ(klines, 9, 3, 3);
  
  // 计算EMA趋势
  const ema21 = calcEMA(klines, 21);
  const currentPrice = parseFloat(klines[klines.length - 1].c);
  
  // 计算成交量信息
  const volumeInfo = calcVolumeTrend(klines, 5);
  
  // 检查数据有效性
  if (fastKD.K === null || slowKD.K === null) {
    return {
      fastKD,
      slowKD,
      signal: 'wait',
      signalStrength: 'none',
      direction: null,
      description: '数据不足，等待更多K线',
      volumeInfo,
      ema21,
      pricePosition: 'unknown'
    };
  }
  
  let signal = 'wait';
  let signalStrength = 'weak';
  let direction = null;
  let description = '观望';
  
  // 判断价格位置
  let pricePosition = 'neutral';
  if (fastKD.K > 80 || slowKD.K > 80) pricePosition = 'overbought';
  else if (fastKD.K < 20 || slowKD.K < 20) pricePosition = 'oversold';
  
  // EMA趋势判断
  let emaTrend = 'neutral';
  if (ema21 !== null) {
    if (currentPrice > ema21 * 1.02) emaTrend = 'uptrend';      // 价格超过EMA 2%认为上升趋势
    else if (currentPrice < ema21 * 0.98) emaTrend = 'downtrend'; // 价格低于EMA 2%认为下降趋势
  }
  
  // 判断金叉死叉
  const fastGoldenCross = fastKD.K > fastKD.D;
  const fastDeathCross = fastKD.K < fastKD.D;
  const slowGoldenCross = slowKD.K > slowKD.D;
  const slowDeathCross = slowKD.K < slowKD.D;
  
  // 双KD同向金叉 - 多头信号
  if (fastGoldenCross && slowGoldenCross) {
    signal = 'buy';
    direction = 'up';
    
    // 计算基础信号强度
    const fastDiff = Math.abs(fastKD.K - fastKD.D);
    const slowDiff = Math.abs(slowKD.K - slowKD.D);
    
    if (fastDiff > 10 && slowDiff > 5) {
      signalStrength = 'strong';
      description = '强势多头信号：双KD同向金叉';
    } else if (fastDiff > 5 && slowDiff > 2) {
      signalStrength = 'medium';
      description = '中等多头信号：双KD同向金叉';
    } else {
      signalStrength = 'weak';
      description = '弱多头信号：双KD轻微金叉';
    }
    
    // 增强过滤条件
    let filterReasons = [];
    
    // 1. 成交量放量确认
    if (!volumeInfo.isVolumeConfirm) {
      signalStrength = signalStrength === 'strong' ? 'medium' : 'weak';
      filterReasons.push('缺乏放量配合');
    }
    
    // 2. 价格位置过滤
    if (pricePosition === 'overbought') {
      if (fastKD.K > 85 && slowKD.K > 85) {
        signal = 'wait';
        signalStrength = 'none';
        filterReasons.push('超买区域风险高');
      } else {
        signalStrength = 'weak';
        filterReasons.push('接近超买区域');
      }
    } else if (pricePosition === 'oversold') {
      // 超卖区域的多头信号更可靠
      signalStrength = signalStrength === 'weak' ? 'medium' : 'strong';
      filterReasons.push('超卖反弹机会');
    }
    
    // 3. EMA趋势过滤
    if (emaTrend === 'downtrend') {
      signalStrength = signalStrength === 'strong' ? 'medium' : 'weak';
      filterReasons.push('逆趋势交易风险');
    } else if (emaTrend === 'uptrend') {
      // 顺势交易，增强信号
      if (signalStrength === 'weak') signalStrength = 'medium';
      filterReasons.push('顺势多头');
    }
    
    // 更新描述
    if (filterReasons.length > 0) {
      description += ` (${filterReasons.join(', ')})`;
    }
  }
  // 双KD同向死叉 - 空头信号
  else if (fastDeathCross && slowDeathCross) {
    signal = 'sell';
    direction = 'down';
    
    // 计算基础信号强度
    const fastDiff = Math.abs(fastKD.K - fastKD.D);
    const slowDiff = Math.abs(slowKD.K - slowKD.D);
    
    if (fastDiff > 10 && slowDiff > 5) {
      signalStrength = 'strong';
      description = '强势空头信号：双KD同向死叉';
    } else if (fastDiff > 5 && slowDiff > 2) {
      signalStrength = 'medium';
      description = '中等空头信号：双KD同向死叉';
    } else {
      signalStrength = 'weak';
      description = '弱空头信号：双KD轻微死叉';
    }
    
    // 增强过滤条件
    let filterReasons = [];
    
    // 1. 成交量放量确认
    if (!volumeInfo.isVolumeConfirm) {
      signalStrength = signalStrength === 'strong' ? 'medium' : 'weak';
      filterReasons.push('缺乏放量配合');
    }
    
    // 2. 价格位置过滤
    if (pricePosition === 'oversold') {
      if (fastKD.K < 15 && slowKD.K < 15) {
        signal = 'wait';
        signalStrength = 'none';
        filterReasons.push('超卖区域风险高');
      } else {
        signalStrength = 'weak';
        filterReasons.push('接近超卖区域');
      }
    } else if (pricePosition === 'overbought') {
      // 超买区域的空头信号更可靠
      signalStrength = signalStrength === 'weak' ? 'medium' : 'strong';
      filterReasons.push('超买回调机会');
    }
    
    // 3. EMA趋势过滤
    if (emaTrend === 'uptrend') {
      signalStrength = signalStrength === 'strong' ? 'medium' : 'weak';
      filterReasons.push('逆趋势交易风险');
    } else if (emaTrend === 'downtrend') {
      // 顺势交易，增强信号
      if (signalStrength === 'weak') signalStrength = 'medium';
      filterReasons.push('顺势空头');
    }
    
    // 更新描述
    if (filterReasons.length > 0) {
      description += ` (${filterReasons.join(', ')})`;
    }
  }
  // KD方向不一致 - 过滤掉假信号
  else {
    signal = 'wait';
    signalStrength = 'none';
    
    if (fastGoldenCross && slowDeathCross) {
      description = '快KD金叉但慢KD死叉，等待确认';
    } else if (fastDeathCross && slowGoldenCross) {
      description = '快KD死叉但慢KD金叉，等待确认';
    } else {
      description = '双KD方向不明确，继续观望';
    }
  }
  
  return {
    fastKD,
    slowKD,
    signal,
    signalStrength,
    direction,
    description,
    // 增强信息
    volumeInfo,
    ema21,
    emaTrend,
    pricePosition,
    currentPrice,
    // 调试信息
    debug: {
      fastGoldenCross,
      fastDeathCross,
      slowGoldenCross,
      slowDeathCross
    }
  };
}

/**
 * 计算EMA指标
 * EMA是指数移动平均线，更加重视近期价格变化，常用于趋势判断
 * 
 * @param {Array} klines - K线数据数组，需包含 {c: close} 属性
 * @param {number} period - 计算周期，默认21
 * @returns {number|null} EMA值，数据不足时返回null
 */
function calcEMA(klines, period = 21) {
  if (!Array.isArray(klines) || klines.length < period) return null;
  
  const multiplier = 2 / (period + 1);
  let ema = 0;
  
  // 使用SMA作为第一个EMA值
  let sma = 0;
  for (let i = klines.length - period; i < klines.length - period + period; i++) {
    sma += parseFloat(klines[i].c);
  }
  ema = sma / period;
  
  // 从SMA之后开始计算EMA
  for (let i = klines.length - period + period; i < klines.length; i++) {
    const close = parseFloat(klines[i].c);
    ema = (close - ema) * multiplier + ema;
  }
  
  return +ema.toFixed(6);
}

/**
 * 计算成交量趋势和放量确认
 * 通过比较最近lookback个周期的成交量，判断是否有放量配合
 * 
 * @param {Array} klines - K线数据数组，需包含 {q: volume} 属性
 * @param {number} lookback - 回望周期，默认5
 * @returns {Object} 包含趋势和放量信息的对象
 */
function calcVolumeTrend(klines, lookback = 5) {
  // 参数验证：需要至少lookback根K线数据
  if (!Array.isArray(klines) || klines.length < lookback) {
    return { trend: 'flat', isVolumeConfirm: false, volumeRatio: 1 };
  }

  const slice = klines.slice(-lookback); // 获取最近lookback个周期的数据
  const firstVol = parseFloat(slice[0].q);                    // 首个周期成交量
  const lastVol = parseFloat(slice[slice.length - 1].q);     // 最后一个周期成交量
  
  // 计算平均成交量
  const avgVolume = slice.reduce((sum, k) => sum + parseFloat(k.q), 0) / lookback;
  const currentVol = parseFloat(klines[klines.length - 1].q);
  const volumeRatio = currentVol / avgVolume;
  
  // 判断成交量趋势
  let trend = 'flat';
  if (lastVol > firstVol * 1.2) trend = 'up';
  else if (lastVol < firstVol * 0.8) trend = 'down';
  
  // 放量确认：当前成交量超过平均成交量1.5倍认为放量
  const isVolumeConfirm = volumeRatio >= 1.5;

  return {
    trend,
    isVolumeConfirm,
    volumeRatio: +volumeRatio.toFixed(2),
    avgVolume: +avgVolume.toFixed(2),
    currentVolume: currentVol
  };
}

/**
 * 计算多空力量对比
 * 通过统计最近lookback个周期中上涨和下跌日的成交量，
 * 衡量多头和空头力量的相对强弱，判断资金流向
 *
 * @param {Array} klines - K线数据数组，需包含 {o: open, c: close, q: volume} 属性
 * @param {number} lookback - 回望周期，默认5
 * @returns {Object} {bullPower: 多头力量, bearPower: 空头力量, powerDiff: 多空差值}
 */
function calcPowerDiff(klines, lookback = 5) {
  // 参数验证：确保输入是有效数组
  if (!Array.isArray(klines) || klines.length === 0) {
    return { bullPower: 0, bearPower: 0, powerDiff: 0 };
  }

  const slice = klines.slice(-lookback); // 获取最近lookback个周期的数据
  let bullPower = 0; // 多头力量：上涨日成交量的总和
  let bearPower = 0; // 空头力量：下跌日成交量的总和

  // 遍历每个周期，统计多空力量
  for (const kline of slice) {
    const open = parseFloat(kline.o);   // 开盘价
    const close = parseFloat(kline.c);  // 收盘价
    const amount = parseFloat(kline.q); // 成交量

    if (close > open) {
      // 上涨：收盘价 > 开盘价，归入多头力量
      bullPower += amount;
    } else if (close < open) {
      // 下跌：收盘价 < 开盘价，归入空头力量
      bearPower += amount;
    }
    // 注：平盘(close === open)的成交量不计入多空任何一方
  }

  // 计算多空力量差值：正值表示多头占优，负值表示空头占优
  const powerDiff = bullPower - bearPower;

  return {
    bullPower: +bullPower.toFixed(2),  // 多头力量，保留2位小数
    bearPower: +bearPower.toFixed(2),  // 空头力量，保留2位小数
    powerDiff: +powerDiff.toFixed(2)   // 多空差值，保留2位小数
  };
}

/**
 * 多币种信号引擎类
 * 负责管理多个交易对的K线数据，并对每个对进行技术分析和信号生成
 * 支持多种技术指标的综合分析，包括RSI、KDJ、成交量趋势、多空力量对比等
 */
class MultiSymbolSignalEngine {
  /**
   * 构造函数
   * @param {number} minForIndicators - 进行完整技术分析所需的最少K线数量，默认30
   * @param {number} maxHistory - 每个币种保存的最大历史K线数量，默认200
   * @param {number} trendMemoryLength - 趋势记忆长度，用于判断连续趋势，默认5
   * @param {number} stuckKlineTimeout - K线卡住超时时间（毫秒），默认90秒
   * @param {Object} autoRecoveryOptions - 自动恢复配置
   */
  constructor(minForIndicators = 30, maxHistory = 200, trendMemoryLength = 5, stuckKlineTimeout = 90000, autoRecoveryOptions = {}) {
    // 参数验证和设置，确保参数在合理范围内
    this.minForIndicators = Math.max(1, minForIndicators);     // 至少1
    this.maxHistory = Math.max(10, maxHistory);               // 至少10
    this.trendMemoryLength = Math.max(1, trendMemoryLength);  // 至少1
    this.stuckKlineTimeout = Math.max(30000, stuckKlineTimeout); // K线卡住超时时间，至少30秒

    // 自动恢复配置
    this.autoRecovery = {
      enabled: autoRecoveryOptions.enabled !== false,                    // 默认开启自动恢复
      strategy: autoRecoveryOptions.strategy || 'force_close',           // 恢复策略: 'force_close' | 'skip' | 'callback'
      maxRetries: autoRecoveryOptions.maxRetries || 3,                   // 最大重试次数
      retryDelay: autoRecoveryOptions.retryDelay || 10000,               // 重试间隔时间（毫秒）
      criticalTimeout: autoRecoveryOptions.criticalTimeout || 300000     // 严重超时时间（5分钟）
    };

    // 使用Map存储币种数据，相比普通对象有更好的性能
    this.symbolData = new Map(); // { symbol -> { historyKlines: [], trendMemory: [], pendingCloseConfirmation: null, lastUpdateTime: null } }

    // 收盘确认事件回调函数
    this.onKlineCloseCallback = null;

    // 卡住K线检查回调函数
    this.onStuckKlineCallback = null;

    // 自动恢复回调函数
    this.onAutoRecoveryCallback = null;

    // 定时器用于检查卡住K线
    this.stuckCheckTimer = null;
    this.stuckCheckInterval = 30000; // 每30秒检查一次
  }

  /**
   * 初始化币种数据结构
   * 为新的交易对创建空的数据容器
   * @param {string} symbol - 交易对符号，如'BTCUSDT'
   */
  initSymbol(symbol) {
    if (!this.symbolData.has(symbol)) {
      this.symbolData.set(symbol, {
        historyKlines: [],              // 历史K线数据
        trendMemory: [],                // 趋势记忆，用于记录最近几次的趋势方向
        pendingCloseConfirmation: null, // 待确认收盘的K线数据
        lastUpdateTime: Date.now(),     // 最后一次更新时间
        isStuck: false,                 // 是否处于卡住状态
        stuckStartTime: null,           // 卡住开始时间
        recoveryAttempts: 0,            // 恢复尝试次数
        lastRecoveryTime: null          // 上次恢复时间
      });
    }
  }

  /**
   * 设置K线收盘确认事件的回调函数
   * @param {Function} callback - 回调函数，参数为(symbol, closedKline, analysisResult)
   */
  setKlineCloseCallback(callback) {
    this.onKlineCloseCallback = callback;
  }

  /**
   * 设置卡住K线检查的回调函数
   * @param {Function} callback - 回调函数，参数为(symbol, stuckDuration, lastKline)
   */
  setStuckKlineCallback(callback) {
    this.onStuckKlineCallback = callback;
  }

  /**
   * 设置自动恢复的回调函数
   * @param {Function} callback - 回调函数，参数为(symbol, strategy, result)
   */
  setAutoRecoveryCallback(callback) {
    this.onAutoRecoveryCallback = callback;
  }

  /**
   * 启动卡住K线检查定时器
   * 自动检查所有交易对的K线是否卡住
   */
  startStuckKlineMonitoring() {
    if (this.stuckCheckTimer) {
      clearInterval(this.stuckCheckTimer); // 清除旧的定时器
    }

    this.stuckCheckTimer = setInterval(() => {
      this.checkStuckKlines();
    }, this.stuckCheckInterval);
  }

  /**
   * 停止卡住K线检查定时器
   */
  stopStuckKlineMonitoring() {
    if (this.stuckCheckTimer) {
      clearInterval(this.stuckCheckTimer);
      this.stuckCheckTimer = null;
    }
  }

  /**
   * 检查所有交易对的K线是否卡住，并根据配置自动处理
   * 卡住的定义：超过指定时间没有新的K线数据更新
   * @returns {Array} 返回卡住的交易对列表
   */
  checkStuckKlines() {
    const now = Date.now();
    const stuckSymbols = [];

    for (const [symbol, data] of this.symbolData.entries()) {
      // 计算距离上次更新的时间
      const timeSinceLastUpdate = now - data.lastUpdateTime;

      // 检查是否超过超时时间
      if (timeSinceLastUpdate > this.stuckKlineTimeout) {
        if (!data.isStuck) {
          // 第一次检测到卡住，初始化卡住状态
          data.isStuck = true;
          data.stuckStartTime = now;
          data.recoveryAttempts = 0;
          data.lastRecoveryTime = null;
        }

        const lastKline = data.historyKlines.length > 0
          ? data.historyKlines[data.historyKlines.length - 1]
          : null;

        const stuckInfo = {
          symbol,
          stuckDuration: timeSinceLastUpdate,
          totalStuckTime: now - data.stuckStartTime,
          lastKline,
          lastUpdateTime: data.lastUpdateTime,
          recoveryAttempts: data.recoveryAttempts
        };

        stuckSymbols.push(stuckInfo);

        // 调用检查回调函数
        if (this.onStuckKlineCallback) {
          this.onStuckKlineCallback(symbol, timeSinceLastUpdate, lastKline);
        }

        // 自动恢复处理
        if (this.autoRecovery.enabled) {
          this.attemptAutoRecovery(symbol, data, stuckInfo);
        }

      } else if (data.isStuck) {
        // 之前卡住但现在恢复了，重置卡住状态
        this.resetStuckState(data);
      }
    }

    return stuckSymbols;
  }

  /**
   * 手动强制解决卡住的K线
   * 对于长时间卡住的K线，可以手动触发收盘确认
   * @param {string} symbol - 交易对符号
   * @param {boolean} forceClose - 是否强制收盘当前待确认的K线
   * @returns {Object|null} 如果有待确认的K线，返回分析结果
   */
  forceResolveStuckKline(symbol, forceClose = true) {
    const data = this.symbolData.get(symbol);
    if (!data) return null;

    // 更新最后一次更新时间
    data.lastUpdateTime = Date.now();
    data.isStuck = false;

    if (forceClose && data.pendingCloseConfirmation) {
      // 强制收盘待确认的K线
      const pendingKline = data.pendingCloseConfirmation;

      // 将待确认的K线添加到历史数据
      data.historyKlines.push({ ...pendingKline, isClosed: true, forceClosed: true });
      data.pendingCloseConfirmation = null;

      // 限制历史数据数量
      if (data.historyKlines.length > this.maxHistory) {
        data.historyKlines.shift();
      }

      // 进行分析
      const analysisResult = this.analyze(symbol);

      // 更新趋势记忆
      if (analysisResult.direction === 'up' || analysisResult.direction === 'down') {
        data.trendMemory.push(analysisResult.direction);
        if (data.trendMemory.length > this.trendMemoryLength) {
          data.trendMemory.shift();
        }
      }

      analysisResult.trendBias = this.calcTrendBias(symbol);
      analysisResult.eventType = 'force_close';
      analysisResult.isForceResolved = true;

      // 调用收盘回调（如果有）
      if (this.onKlineCloseCallback) {
        this.onKlineCloseCallback(symbol, pendingKline, analysisResult);
      }

      return analysisResult;
    }

    return null;
  }

  /**
   * 重置卡住状态
   * @param {Object} data - 交易对数据
   */
  resetStuckState(data) {
    data.isStuck = false;
    data.stuckStartTime = null;
    data.recoveryAttempts = 0;
    data.lastRecoveryTime = null;
  }

  /**
   * 尝试自动恢复卡住的K线
   * @param {string} symbol - 交易对符号
   * @param {Object} data - 交易对数据
   * @param {Object} stuckInfo - 卡住信息
   */
  attemptAutoRecovery(symbol, data, stuckInfo) {
    const now = Date.now();

    // 检查是否超过最大重试次数
    if (data.recoveryAttempts >= this.autoRecovery.maxRetries) {
      return; // 已经超过最大重试次数
    }

    // 检查是否需要等待重试间隔
    if (data.lastRecoveryTime && (now - data.lastRecoveryTime) < this.autoRecovery.retryDelay) {
      return; // 还在重试间隔期内
    }

    // 记录恢复尝试
    data.recoveryAttempts++;
    data.lastRecoveryTime = now;

    let recoveryResult = null;

    try {
      switch (this.autoRecovery.strategy) {
        case 'force_close':
          // 强制收盘策略：强制收盘待确认的K线
          recoveryResult = this.forceResolveStuckKline(symbol, true);
          break;

        case 'skip':
          // 跳过策略：重置更新时间，但不强制收盘
          data.lastUpdateTime = now;
          this.resetStuckState(data);
          recoveryResult = {
            symbol,
            strategy: 'skip',
            success: true,
            message: '跳过卡住K线，重置更新时间'
          };
          break;

        case 'callback':
          // 回调策略：只通知，不自动处理
          recoveryResult = {
            symbol,
            strategy: 'callback',
            success: false,
            message: '等待外部处理'
          };
          break;

        default:
          recoveryResult = {
            symbol,
            strategy: 'unknown',
            success: false,
            message: '未知的恢复策略'
          };
      }

      // 调用自动恢复回调
      if (this.onAutoRecoveryCallback) {
        this.onAutoRecoveryCallback(symbol, this.autoRecovery.strategy, {
          ...recoveryResult,
          attempt: data.recoveryAttempts,
          stuckDuration: stuckInfo.stuckDuration,
          totalStuckTime: stuckInfo.totalStuckTime
        });
      }

    } catch (error) {
      // 恢复失败
      const errorResult = {
        symbol,
        strategy: this.autoRecovery.strategy,
        success: false,
        error: error.message,
        attempt: data.recoveryAttempts
      };

      if (this.onAutoRecoveryCallback) {
        this.onAutoRecoveryCallback(symbol, this.autoRecovery.strategy, errorResult);
      }
    }
  }

  /**
   * 处理上一根K线的收盘确认事件
   * 当新K线开始时，对上一根K线进行最终确认和分析
   * @param {string} symbol - 交易对符号
   * @param {Object} newKline - 新的K线数据
   * @returns {Object|null} 如果有上一根K线需要确认，返回分析结果，否则返回null
   */
  handlePreviousKlineClose(symbol, newKline) {
    const data = this.symbolData.get(symbol);
    if (!data || !data.pendingCloseConfirmation) {
      return null; // 没有待确认的K线
    }

    // 获取待确认的K线数据
    const pendingKline = data.pendingCloseConfirmation;

    // 检查是否是新的K线周期（通过时间戳判断）
    if (newKline.t && pendingKline.t && newKline.t > pendingKline.t) {
      // 确认上一根K线已经收盘，将其添加到历史数据中
      data.historyKlines.push({ ...pendingKline, isClosed: true });

      // 清除待确认状态
      data.pendingCloseConfirmation = null;

      // 限制历史数据数量
      if (data.historyKlines.length > this.maxHistory) {
        data.historyKlines.shift();
      }

      // 对已收盘的K线进行分析
      const analysisResult = this.analyze(symbol);

      // 记录趋势方向到记忆中
      if (analysisResult.direction === 'up' || analysisResult.direction === 'down') {
        data.trendMemory.push(analysisResult.direction);
        if (data.trendMemory.length > this.trendMemoryLength) {
          data.trendMemory.shift();
        }
      }

      // 计算趋势偏向
      analysisResult.trendBias = this.calcTrendBias(symbol);
      analysisResult.eventType = 'kline_close'; // 标记事件类型

      // 调用回调函数（如果设置了）
      if (this.onKlineCloseCallback) {
        this.onKlineCloseCallback(symbol, pendingKline, analysisResult);
      }

      return analysisResult;
    }

    return null; // 不是新周期，不需要确认
  }

  /**
   * 推入新的K线数据并进行信号分析
   * 该方法为核心接口，处理实时K线数据并返回技术分析结果
   *
   * @param {string} symbol - 交易对符号，如'BTCUSDT'
   * @param {Object} kline - K线数据对象，包含{o, h, l, c, q}等字段
   * @param {boolean} isFinal - 是否为最终K线（true：新增；false：更新最后一根）
   * @returns {Object} 技术分析结果，包含信号、方向、强度等信息
   */
  pushKline(symbol, kline, isFinal = false) {
    // 输入参数验证
    if (!symbol || typeof symbol !== 'string') {
      throw new Error('Symbol must be a non-empty string');
    }
    if (!kline || typeof kline !== 'object') {
      throw new Error('Kline must be a valid object');
    }

    // 初始化该币种的数据结构
    this.initSymbol(symbol);
    const data = this.symbolData.get(symbol);

    // 更新最后一次更新时间，用于卡住K线检查
    data.lastUpdateTime = Date.now();

    // 如果之前是卡住状态，现在恢复了
    if (data.isStuck) {
      data.isStuck = false;
    }

    // 先处理上一根K线的收盘确认事件（如果有）
    const closeConfirmationResult = this.handlePreviousKlineClose(symbol, kline);

    // 根据isFinal参数决定是新增K线还是更新最后一根K线
    if (isFinal) {
      // 最终K线：直接添加到历史数据中
      data.historyKlines.push(kline);
      // 清除待确认状态
      data.pendingCloseConfirmation = null;
    } else {
      // 实时K线：先检查是否需要处理上一根K线的收盘
      if (data.historyKlines.length > 0 && !data.historyKlines.at(-1).isClosed) {
        // 如果最后一根K线还未收盘，则更新它
        data.historyKlines[data.historyKlines.length - 1] = kline;
      } else {
        // 新的K线周期：设置为待确认状态
        data.pendingCloseConfirmation = { ...kline };
        data.historyKlines.push(kline);
      }
    }

    // 限制历史数据数量，避免内存溢出
    if (data.historyKlines.length > this.maxHistory) {
      data.historyKlines.shift(); // 删除最早的K线
    }

    // 执行技术分析，生成交易信号
    const result = this.analyze(symbol);
    result.eventType = isFinal ? 'kline_final' : 'kline_update';

    // 只有在确认收盘的K线时才更新趋势记忆（避免实时数据干扰）
    if (isFinal && (result.direction === 'up' || result.direction === 'down')) {
      data.trendMemory.push(result.direction);
      // 限制记忆长度
      if (data.trendMemory.length > this.trendMemoryLength) {
        data.trendMemory.shift(); // 删除最早的趋势记录
      }
    }

    // 计算并添加趋势偏向信息
    result.trendBias = this.calcTrendBias(symbol);

    // 如果有收盘确认事件，将其信息附加到结果中
    if (closeConfirmationResult) {
      result.previousKlineClose = closeConfirmationResult;
    }

    return result;
  }

  // 分析某个币的信号
  analyze(symbol) {
    if (!symbol || !this.symbolData.has(symbol)) {
      return { status: 'error', reason: '无效的symbol或数据不存在', direction: null };
    }

    const data = this.symbolData.get(symbol);
    const len = data.historyKlines.length;

    if (len < 2) {
      return { status: 'insufficient', reason: '历史K线不足2根', direction: null };
    }

    const lastKline = data.historyKlines.at(-2);
    const nowKline = data.historyKlines.at(-1);

    // 简化模式
    if (len < this.minForIndicators) {
      const lastClose = parseFloat(lastKline.c);
      const nowClose = parseFloat(nowKline.c);
      const lastAmount = parseFloat(lastKline.q);
      const nowAmount = parseFloat(nowKline.q);

      const direction = nowClose > lastClose ? 'up' :
        nowClose < lastClose ? 'down' : 'flat';
      const volumeSpike = nowAmount > lastAmount * 1.5;

      return {
        symbol,
        status: 'simple',
        reason: `历史K线不足${this.minForIndicators}根，使用简化判断`,
        direction,
        volumeSpike,
        signal: volumeSpike
          ? (direction === 'up' ? '可能多头加速' :
            direction === 'down' ? '可能空头加速' : '观望')
          : '无明显信号',
        strength: '未知'
      };
    }

    // 完整模式 - 使用增强版双KD变速过滤法
    const rsi = calcRSI(data.historyKlines, 14);
    const { K, D, J } = calcKDJ(data.historyKlines, 9, 3, 3);
    const { bullPower, bearPower, powerDiff } = calcPowerDiff(data.historyKlines, 5);
    
    // 使用增强版双KD变速过滤法分析
    const dualKDSignal = calcDualKD(data.historyKlines);

    // 使用双KD信号来决定最终方向
    let direction = 'flat';
    let signal = '观望';
    let strength = '弱';
    
    // 优先使用增强版双KD信号判断方向
    if (dualKDSignal.signal === 'buy' && dualKDSignal.signalStrength !== 'none') {
      direction = 'up';
      signal = `多头信号: ${dualKDSignal.description}`;
      strength = dualKDSignal.signalStrength === 'strong' ? '强' : 
                 dualKDSignal.signalStrength === 'medium' ? '中' : '弱';
    } else if (dualKDSignal.signal === 'sell' && dualKDSignal.signalStrength !== 'none') {
      direction = 'down';
      signal = `空头信号: ${dualKDSignal.description}`;
      strength = dualKDSignal.signalStrength === 'strong' ? '强' : 
                 dualKDSignal.signalStrength === 'medium' ? '中' : '弱';
    } else {
      // 双KD无明确信号时，回退到原有逻辑
      if (rsi >= 50 && J > K && dualKDSignal.volumeInfo.trend === 'up' && powerDiff > 0) {
        direction = 'up';
        signal = '多头占优，资金流入（原有逻辑）';
      } else if (rsi <= 50 && J < K && dualKDSignal.volumeInfo.trend === 'down' && powerDiff < 0) {
        direction = 'down';
        signal = '空头占优，资金流出（原有逻辑）';
      } else {
        direction = 'flat';
        signal = `等待信号: ${dualKDSignal.description}`;
      }
      
      // 原有的超买超卖判断
      if (rsi > 70 && J > 80) signal = '可能超买，空头预警';
      if (rsi < 30 && J < 20) signal = '可能超卖，多头机会';
      
      const absDiff = Math.abs(powerDiff);
      if (absDiff > 50000) strength = '强';
      else if (absDiff > 20000) strength = '中';
    }

    return {
      symbol,
      status: 'full',
      reason: '实时模式 - 使用增强版双KD变速过滤法（含成交量、价格位置、EMA趋势过滤）',
      rsi,
      K, D, J,
      // 增强版双KD信息
      fastKD: dualKDSignal.fastKD,
      slowKD: dualKDSignal.slowKD,
      dualKDSignal: dualKDSignal.signal,
      dualKDStrength: dualKDSignal.signalStrength,
      dualKDDescription: dualKDSignal.description,
      // 增强过滤信息
      volumeInfo: dualKDSignal.volumeInfo,
      ema21: dualKDSignal.ema21,
      emaTrend: dualKDSignal.emaTrend,
      pricePosition: dualKDSignal.pricePosition,
      currentPrice: dualKDSignal.currentPrice,
      // 原有信息
      volumeTrend: dualKDSignal.volumeInfo.trend, // 保持兼容性
      bullPower,
      bearPower,
      powerDiff,
      direction,
      signal,
      strength
    };
  }

  /**
   * 计算指定币种的趋势偏向
   * 根据最近几次的趋势方向记忆，判断该币种是否处于连续上涨、下跌或震荡状态
   * 这对于趋势跟随策略很有用
   *
   * @param {string} symbol - 交易对符号
   * @returns {string} 趋势偏向描述：'连续多头趋势'|'连续空头趋势'|'震荡'|'无明显趋势'
   */
  calcTrendBias(symbol) {
    const data = this.symbolData.get(symbol);
    if (!data) return '无明显趋势'; // 数据不存在

    // 趋势记忆数据不足，无法判断
    if (data.trendMemory.length < this.trendMemoryLength) return '无明显趋势';

    // 统计趋势记忆中的上涨和下跌次数
    const ups = data.trendMemory.filter(d => d === 'up').length;
    const downs = data.trendMemory.filter(d => d === 'down').length;

    // 如果70%以上的时间都是上涨，认为是连续多头趋势
    if (ups >= this.trendMemoryLength * 0.7) return '连续多头趋势';

    // 如果70%以上的时间都是下跌，认为是连续空头趋势
    if (downs >= this.trendMemoryLength * 0.7) return '连续空头趋势';

    // 其他情况认为是震荡市
    return '震荡';
  }
  /**
   * 销毁引擎，清理资源
   * 在不再使用引擎时调用，防止内存泄漏
   */
  destroy() {
    // 停止卡住K线检查定时器
    this.stopStuckKlineMonitoring();

    // 清理所有数据
    this.symbolData.clear();

    // 清理回调函数
    this.onKlineCloseCallback = null;
    this.onStuckKlineCallback = null;
  }

  /**
   * 获取引擎的状态统计信息
   * @returns {Object} 包含各种统计信息的对象
   */
  getStats() {
    const stats = {
      totalSymbols: this.symbolData.size,
      stuckSymbols: 0,
      symbolsWithPendingClose: 0,
      totalHistoryKlines: 0,
      averageHistoryLength: 0
    };

    for (const [, data] of this.symbolData.entries()) {
      if (data.isStuck) stats.stuckSymbols++;
      if (data.pendingCloseConfirmation) stats.symbolsWithPendingClose++;
      stats.totalHistoryKlines += data.historyKlines.length;
    }

    if (stats.totalSymbols > 0) {
      stats.averageHistoryLength = Math.round(stats.totalHistoryKlines / stats.totalSymbols);
    }

    return stats;
  }
}

export default {
  MultiSymbolSignalEngine, // 多币种信号引擎主类
  calcRSI,                 // RSI指标计算函数
  calcKDJ,                 // KDJ指标计算函数
  calcDualKD,              // 增强版双KD变速过滤法计算函数
  calcEMA,                 // EMA指标计算函数
  calcVolumeTrend,         // 增强版成交量趋势计算函数
  calcPowerDiff            // 多空力量对比计算函数
};
